﻿#region Copyright(c) 2006 ZO, All right reserved.
// -----------------------------------------------------------------------------
// Copyright(c) 2006 ZO, All right reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   1.  Redistribution of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//   2.  Redistribution in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//   3.  The name of the author may not be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// -----------------------------------------------------------------------------
#endregion


#region Using directives

using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using ZO.SmartCore.Text;
#endregion

namespace ZO.SmartCore.Xml
{
    /// <summary>
    /// The Document object represents the entire XML document.
    /// </summary>
    /// <remarks>
    /// The Document object is the root-node of a document. All nodes in a node-tree are childnodes of the Document object.
    /// </remarks>
    public class Document : Node
    {

        #region Constructors

        #endregion

        #region Destructor

        #endregion

        #region Fields
        private CharacterEncoding _Encoding = CharacterEncoding.UTF8;

        private const string DEFAULT_VERSION = "1.0";
        private string _Version = DEFAULT_VERSION;


        #endregion

        #region Events
        #endregion

        #region Operators

        #endregion

        #region Properties
        /// <summary>
        /// When overridden in a derived class, gets the type of the current node.
        /// </summary>
        /// <value></value>
        /// <returns>One of the <see cref="NodeType"></see> values.</returns>
        public override NodeType NodeType
        {
            get { return NodeType.Document; }
        }

        /// <summary>
        /// Represents a character encoding for Document. 
        /// </summary>
        public virtual CharacterEncoding Encoding
        {
            get
            {
                return this._Encoding;
            } // get
            set
            {
                this._Encoding = value;
            } // set
        } // Encoding

        /// <summary>Gets the root <see cref="Element"></see> for the document.</summary>
        /// <returns>The Element that represents the root of the XML document tree. If no root exists, null is returned.</returns>
        public Element DocumentElement
        {
            get
            {
                return (Element)this.FindChild(NodeType.Element);
            }
            set
            {
                if (value != null)
                {
                    Element root = (Element)this.FindChild(NodeType.Element);
                    if (root == null)
                    {
                        this.ChildNodes.Add(value);
                    }
                    else
                    {
                        root = value;
                    }
                }
            }
        }

        /// <summary>
        /// defines the XML version
        /// </summary>
        public string Version
        {
            get { return _Version; }
            set
            {
                _Version = value;
            }
        }
        #endregion

        #region Methods

        /// <summary>
        /// Clears the Document
        /// </summary>
        public void Clear()
        {
            this.ChildNodes.Clear();
        }



        /// <summary>Loads the XML document from the specified string.</summary>
        /// <param name="xml">String containing the XML document to load. </param>
        /// <exception cref="System.Xml.XmlException">There is a load or parse error in the XML. In this case, the document remains empty. </exception>
        [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings")]
        public virtual void LoadXml(string xml)
        {

            if (String.IsNullOrEmpty(xml))
            {
                return;
            }

            XmlReaderSettings settings = new XmlReaderSettings();

            settings.CloseInput = true;
            settings.IgnoreWhitespace = true;
            settings.ProhibitDtd = true;
            settings.ValidationFlags = XmlSchemaValidationFlags.None;
            settings.ValidationType = ValidationType.None;
            settings.ConformanceLevel = ConformanceLevel.Auto;
            Element rootElement = null;

            using (XmlReader reader = XmlReader.Create(new XmlTextReader(new StringReader(xml)), settings))
            //using (XmlReader reader = new XmlTextReader(new StringReader(xml)))
            {
                //goto Top root node object
                while (reader.Read())
                {

                    if (reader.NodeType == XmlNodeType.XmlDeclaration)
                    {
                        while (reader.MoveToNextAttribute())
                        {
                            if (reader.Name == "version")
                            {
                                this.Version = reader.Value;
                            }

                            if (reader.Name == "encoding")
                            {
                                this.Encoding = GetEncoding(reader.Value);
                            }
                        }
                    }

                    if (!reader.EOF && reader.NodeType == XmlNodeType.Comment)
                    {
                        base.AddChild(new Comment(reader.Value));
                    } // if

                    if (reader.NodeType == XmlNodeType.Text)
                    {
                        //this.ChildNodes.Add(new Text(reader.Value));
                        if (rootElement != null)
                        {
                            rootElement.Value = reader.Value;
                        }
                    }

                    if (reader.NodeType == XmlNodeType.CDATA)
                    {
                        if (rootElement != null)
                        {
                            rootElement.AddChild(new CDATA(reader.Value));
                        }

                    }

                    if (!reader.EOF && reader.NodeType == XmlNodeType.Element)
                    {
                        if (reader.Name.Contains(":"))
                        {
                            int index = reader.Name.IndexOf(":");
                            string prefix = reader.Name.Substring(0, index);
                            rootElement = new Element(reader.Name.Substring(++index));
                            rootElement.Prefix = prefix;
                        } // if
                        else
                        {
                            rootElement = new Element(reader.Name);
                        } // else

                        base.AddChild(rootElement);

                        while (reader.MoveToNextAttribute())
                        {
                            if (reader.Name.StartsWith("xmlns"))
                            {
                                if (reader.Name == "xmlns")
                                {
                                    rootElement.Namespaces.DefaultNamespace = reader.Value;
                                } // if
                                else
                                {
                                    int index = reader.Name.IndexOf(":");
                                    string prefix = reader.Name.Substring(++index);
                                    rootElement.Namespaces.Add(prefix, reader.Value);
                                } // else

                            } // if
                            else
                            {
                                if (reader.Name == "xml:lang")
                                {
                                    rootElement.XmlLang = GetLanguage(reader.Value);
                                }
                                else
                                {
                                    if (reader.Name.Contains(":"))
                                    {
                                        int index = reader.Name.IndexOf(":");
                                        string prefix = reader.Name.Substring(0, index);
                                        rootElement.Attributes.Add(reader.Name.Substring(++index), reader.Value, prefix);
                                    } // if
                                    else
                                    {
                                        rootElement.Attributes.Add(reader.Name, reader.Value);
                                    } // else


                                }

                            } // else

                            /*                    if (rootElement.Prefix == reader.Prefix)
                                                {
                                                    if (String.IsNullOrEmpty(rootElement.Namespaces.DefaultNamespace))
                                                    {
                                                        rootElement.Namespaces.DefaultNamespace = reader.NamespaceURI;
                                                    }
                                                }*/


                        } // while

                        if (!reader.EOF)
                        {
                            ReadXml(reader, rootElement);
                        }
                    } // if
                } // while
            }
        }








        ///// <summary>Loads the XML document from the specified URL.</summary>
        /// <param name="fileName">URL for the file containing the XML document to load. </param>
        /// <exception cref="System.Xml.XmlException">There is a load or parse error in the XML. In this case, the document remains empty. </exception>
        [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings")]
        public virtual void Load(string fileName)
        {

            if (String.IsNullOrEmpty(fileName))
            {
                return;
            }

            if (!File.Exists(fileName))
            {
                return;
            }
            XmlReaderSettings settings = new XmlReaderSettings();

            settings.CloseInput = true;
            settings.IgnoreWhitespace = true;
            settings.ProhibitDtd = true;
            settings.ValidationFlags = XmlSchemaValidationFlags.None;
            settings.ValidationType = ValidationType.None;

            using (XmlReader reader = XmlReader.Create(fileName, settings))
            {

                Element rootElement = null;

                //goto Top root node object
                while (reader.Read())
                {

                    if (reader.NodeType == XmlNodeType.XmlDeclaration)
                    {
                        while (reader.MoveToNextAttribute())
                        {

                            if (reader.Name == "version")
                            {
                                this.Version = reader.Value;
                            }

                            if (reader.Name == "encoding")
                            {
                                this.Encoding = GetEncoding(reader.Value);
                            }
                        }
                    }

                    if (reader.NodeType == XmlNodeType.Text)
                    {
                        //this.ChildNodes.Add(new Text(reader.Value));
                        if (rootElement != null)
                        {
                            rootElement.Value = reader.Value;
                        }
                    }


                    if (!reader.EOF && reader.NodeType == XmlNodeType.Comment)
                    {
                        base.AddChild(new Comment(reader.Value));
                    } // if

                    if (!reader.EOF && reader.NodeType == XmlNodeType.Element)
                    {
                        if (reader.Name.Contains(":"))
                        {
                            int index = reader.Name.IndexOf(":");
                            string prefix = reader.Name.Substring(0, index);
                            rootElement = new Element(reader.Name.Substring(++index));
                            rootElement.Prefix = prefix;
                        } // if
                        else
                        {
                            rootElement = new Element(reader.Name);
                        } // else

                        base.AddChild(rootElement);

                        while (reader.MoveToNextAttribute())
                        {
                            if (reader.Name.StartsWith("xmlns"))
                            {

                                if (reader.Name == "xmlns")
                                {
                                    rootElement.Namespaces.DefaultNamespace = reader.Value;
                                } // if
                                else
                                {
                                    int index = reader.Name.IndexOf(":");
                                    string prefix = reader.Name.Substring(++index);
                                    rootElement.Namespaces.Add(prefix, reader.Value);
                                } // else

                            } // if
                            else
                            {
                                if (reader.Name == "xml:lang")
                                {
                                    rootElement.XmlLang = GetLanguage(reader.Value);
                                }
                                else
                                {
                                    if (reader.Name.Contains(":"))
                                    {
                                        int index = reader.Name.IndexOf(":");
                                        string prefix = reader.Name.Substring(0, index);
                                        rootElement.Attributes.Add(reader.Name.Substring(++index), reader.Value, prefix);
                                    } // if
                                    else
                                    {
                                        rootElement.Attributes.Add(reader.Name, reader.Value);
                                    } // else
                                }
                            } // else
                        } // while

                        if (!reader.EOF)
                        {
                            ReadXml(reader, rootElement);
                        }
                    } // if
                } // while
            }

        }



        /// <summary>Saves the XML document to the specified file.</summary>
        /// <param name="fileName">The location of the file where you want to save the document. </param>
        /// <exception cref="System.Xml.XmlException">The operation would not result in a well formed XML document (for example, no document element or duplicate XML declarations). </exception>
        public virtual void Save(string fileName)
        {
            using (StreamWriter writer = new StreamWriter(fileName))
            {
                writer.Write(base.ToString());
                writer.Flush();
            }
        }





        [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
        private static void ReadXml(XmlReader reader, Element element)
        {

            Node currentNode = element;
            bool IsSingleNode;

            while (reader.Read())
            {

                if (reader.NodeType == XmlNodeType.Text)
                {
                    if (currentNode != null)
                    {
                        currentNode.Value = reader.Value;
                    }
                }

                if (reader.NodeType == XmlNodeType.Comment)
                {
                    currentNode.AddChild(new Comment(reader.Value));
                } // if

                if (reader.NodeType == XmlNodeType.EndElement)
                {
                    if (currentNode.Parent != null)
                    {
                        currentNode = currentNode.Parent;
                    }
                }

                if (reader.NodeType == XmlNodeType.Text)
                {
                    currentNode.Value = reader.Value;
                }


                if (reader.NodeType == XmlNodeType.CDATA)
                {
                    if (currentNode is Element)
                    {
                        currentNode.AddChild(new CDATA(reader.Value));
                    }

                }


                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.IsEmptyElement)
                    {
                        IsSingleNode = true;
                    }
                    else
                    {
                        IsSingleNode = false;
                    } // else
                    Element node;

                    if (reader.Name.Contains(":"))
                    {
                        int index = reader.Name.IndexOf(":");
                        string prefix = reader.Name.Substring(0, index);
                        node = new Element(reader.Name.Substring(++index));
                        node.Prefix = prefix;
                    }
                    else
                    {
                        node = new Element(reader.Name);

                    } // else


                    if (reader.HasAttributes)
                    {
                        while (reader.MoveToNextAttribute())
                        {
                            if (reader.Name.StartsWith("xmlns"))
                            {
                                if (reader.Name == "xmlns")
                                {
                                    node.Namespaces.DefaultNamespace = reader.Value;
                                } // if
                                else
                                {
                                    int index = reader.Name.IndexOf(":");
                                    string prefix = reader.Name.Substring(++index);

                                    node.Namespaces.Add(prefix, reader.Value);

                                } // else

                            } // if
                            else
                            {
                                if (reader.Name == "xml:lang")
                                {
                                    node.XmlLang = GetLanguage(reader.Value);
                                }
                                else
                                {
                                    if (reader.Name.Contains(":"))
                                    {
                                        int index = reader.Name.IndexOf(":");
                                        string prefix = reader.Name.Substring(0, index);
                                        node.Attributes.Add(reader.Name.Substring(++index), reader.Value, prefix);
                                    } // if
                                    else
                                    {
                                        node.Attributes.Add(reader.Name, reader.Value);
                                    } // else
                                }
                            } // else
                            //node.Attributes.Add(reader.Name, reader.Value);
                        } // while

                    }

                    //if (node.Prefix == reader.Prefix)
                    //{
                    //    if (String.IsNullOrEmpty(node.Namespaces.DefaultNamespace))
                    //    {
                    //        node.Namespaces.DefaultNamespace = reader.NamespaceURI;
                    //    }
                    //}


                    currentNode.AddChild(node);

                    if (!IsSingleNode)
                    {
                        currentNode = node;
                    }

                } // if
            } // while
        }// ReadElements



        /// <summary>
        /// Gets the language.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <returns></returns>
        internal static XmlLang GetLanguage(string name)
        {

            if (String.Compare(name, "en", true, CultureInfo.InvariantCulture) == 0)
            {
                return XmlLang.English;
            }

            return XmlLang.None;
        }


        /// <summary>
        /// Gets the language.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <returns></returns>
        internal static CharacterEncoding GetEncoding(string name)
        {
            name = name.ToLowerInvariant();

            if (String.Compare(name, "us-ascii", true, CultureInfo.InvariantCulture) == 0)
            {
                return CharacterEncoding.ASCII;
            }

            if (String.Compare(name, "iso-8859-1", true, CultureInfo.InvariantCulture) == 0)
            {
                return CharacterEncoding.ISO88591;
            }

            if (String.Compare(name, "utf-16", true, CultureInfo.InvariantCulture) == 0)
            {
                return CharacterEncoding.Unicode;
            }

            if (String.Compare(name, "utf-8", true, CultureInfo.InvariantCulture) == 0)
            {
                return CharacterEncoding.UTF8;
            }

            return CharacterEncoding.None;
        }

        /// <summary>
        /// Adds the specified node to the end of the list of child nodes, of this node.
        /// </summary>
        /// <param name="newChild">The node to add</param>
        /// <remarks>As only one can child exists in Document, it replaces the root element if exists</remarks>
        public override void AddChild(Node newChild)
        {
            if (newChild == null)
            {
                return;
            }


            if (newChild.NodeType == NodeType.Element)
            {
                this.DocumentElement = newChild as Element;
            }
            if (newChild.NodeType == NodeType.Comment)
            {
                base.AddChild(newChild);
            }

            if (newChild.NodeType == NodeType.ProcessingInstruction)
            {
                base.AddChild(newChild);
            }
        }

        /// <summary>
        /// Adds the processing instruction.
        /// </summary>
        /// <param name="pi">The pi.</param>
        public virtual void AddProcessingInstruction(ProcessingInstruction pi)
        {
            this.AddProcessingInstruction(pi);
        }









        #endregion
    }
}
